/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：太平金科-基础组件封装层-SpringBoot组件封装-核心依赖
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taipingframework.boot.web.support;

import cn.hutool.core.io.IoUtil;
import cn.hutool.http.HTMLFilter;
import com.taipingframework.utility.constant.SystemConstant;
import io.jsonwebtoken.lang.Collections;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.Arrays;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
//import org.springframework.web.multipart.MultipartFile;
//import org.springframework.web.multipart.MultipartHttpServletRequest;
//import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.util.WebUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 参考源码 org.springframework.web.util.ContentCachingRequestWrapper 改造而来！
 * <p>
 * 说明：spring-web提供的ContentCachingRequestWrapper不是特别好用，
 * 如果我们在过滤器或者拦截器中主动读取请求报文，当在Controller层再次使用注解@RequestBody
 * 获取请求入参时，系统依旧会提示我们 `Required request body is missing:...`。
 * <p>
 * 另外，在后端为防止XSS攻击依然需要我们主动对请求入参进行特别处理，spring为我们的开发帶来了便利，
 * 但并不意味着它可以为我们完成所有的工作！
 */
public final class RepeatableRequestWrapper extends HttpServletRequestWrapper {

    private ByteArrayOutputStream cachedContent;
    private RepeatableInputStream inputStream;
    private BufferedReader reader;
    private Collection<Part> parts;

    public RepeatableRequestWrapper(HttpServletRequest request) throws IOException, ServletException {
        super(request);

        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        // 检查请求是否为多部分（即文件上传）
        if (multipartResolver.isMultipart(request)) {
            // 缓存表单数据
            this.parts = request.getParts();

            // MultipartHttpServletRequest multipartRequest = new CommonsMultipartResolver().resolveMultipart(request);
            // MultipartFile file = multipartRequest.getFile("file");
        } else {
            // 缓存请求体
            getInputStream();
        }
    }

    /**
     * ...
     */
    public byte[] getContentAsByteArray() throws IOException {
        if (cachedContent == null) {
            int contentLength = getRequest().getContentLength();
            this.cachedContent = contentLength <= SystemConstant.DEFAULT_INVALID
                    ? new ByteArrayOutputStream()
                    : new ByteArrayOutputStream(contentLength);

            byte[] byteArray = IoUtil.readBytes(getInputStream());
            this.cachedContent.write(byteArray);
        }

        return this.cachedContent.toByteArray();
    }

    @Override
    public RepeatableInputStream getInputStream() throws IOException {
        if (this.inputStream == null) {
            this.inputStream = new RepeatableInputStream(getRequest().getInputStream(), getCharacterEncoding());
        }

        if (this.inputStream.isFinished()) {
            // 此处一定要显式调用reset()方法，内部ByteArrayInputStream对象才能被重复读取！
            this.inputStream.reset();
        }

        return this.inputStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        if (this.reader == null) {
            this.reader = new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
        }
        return this.reader;
    }

    @Override
    public String getCharacterEncoding() {
        String enc = super.getCharacterEncoding();
        return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return super.getParameterNames();
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> parameterMap = super.getParameterMap();
        if (Collections.isEmpty(parameterMap)) {
            return parameterMap;
        }

        LinkedHashMap<String, String[]> mapResult = new LinkedHashMap<>();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            mapResult.put(entry.getKey(), filter(entry.getValue()));
        }

        return mapResult;
    }

    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (StringUtils.isNotEmpty(value)) {
            return HTMLFilter.htmlSpecialChars(value);
        }
        return value;
    }

    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (StringUtils.isNotEmpty(value)) {
            return HTMLFilter.htmlSpecialChars(value);
        }
        return value;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] valueArray = super.getParameterValues(name);
        if (Arrays.isNullOrEmpty(valueArray)) {
            return valueArray;
        }

        return filter(valueArray);
    }

    @Override
    public Collection<Part> getParts() {
        if (Collections.isEmpty(this.parts)) {
            this.parts = new ArrayList<>();
        }
        return this.parts;
    }

    @Contract("_ -> param1")
    private static String[] filter(@NotNull String[] valueArray) {
        String value;
        for (int i = 0; i < valueArray.length; i++) {
            value = valueArray[i];
            if (StringUtils.isNotEmpty(value)) {
                valueArray[i] = HTMLFilter.htmlSpecialChars(value);
            }
        }
        return valueArray;
    }

}
